package networktraffic

import (
	"context"
	"fmt"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/klog/v2"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
	"sigs.k8s.io/scheduler-plugins/apis/config"
	"time"
)

const (
	// Name is the name of the plugin used in the Registry and configurations.
	Name = "network-traffic"
	//Name = "sample-plugin"

	preFilterStateKey = "PreFilter" + Name
	// ErrReasonConstraintsNotMatch is used for PodTopologySpread filter error.
	ErrReasonConstraintsNotMatch = "node(s) didn't match pod topology spread constraints"
	// ErrReasonNodeLabelNotMatch is used when the node doesn't hold the required label.
	ErrReasonNodeLabelNotMatch = ErrReasonConstraintsNotMatch + " (missing required label)"
)

// NetworkTraffic is a score plugin that favors nodes based on their
// network traffic amount. Nodes with less traffic are favored.
// Implements framework.ScorePlugin
type NetworkTraffic struct {
	handle     framework.Handle
	prometheus *PrometheusHandle
}

var _ framework.PreFilterPlugin = &NetworkTraffic{}
var _ framework.FilterPlugin = &NetworkTraffic{}
var _ = framework.ScorePlugin(&NetworkTraffic{})

//type Sample struct {
//	args *SampleArgs
//	handle *framework.Handle
//}

type NetworkTrafficArgs struct {
	// Address of the Prometheus Server
	Address string `json:"address,omitempty"`
	// NetworkInterface to be monitored, assume that nodes OS is homogeneous
	NetworkInterface string `json:"network_interface,omitempty"`
	// TimeRangeInMinutes used to aggregate the network metrics
	//TimeRangeInMinutes int64 `json:"time_range_in_minutes,omitempty"`
	TimeRangeInMinutes int64 `json:"time_range_in_minutes,omitempty"`
}

func getNetworkTrafficArgs(obj runtime.Object) (*NetworkTrafficArgs, error) {
	sa := &NetworkTrafficArgs{}
	if err := frameworkruntime.DecodeInto(obj, sa); err != nil {
		return nil, err
	}
	return sa, nil
}

type preFilterState struct {
	framework.Resource // requests, limits
}

func (s *preFilterState) Clone() framework.StateData {
	return s
}

func computerPodResourceLimit(pod *v1.Pod) *preFilterState {
	result := &preFilterState{}
	for _, container := range pod.Spec.Containers {
		result.Add(container.Resources.Limits)
	}
	return result
}

func getPreFilterState(state *framework.CycleState) (*preFilterState, error) {
	data, err := state.Read(preFilterStateKey)
	if err != nil {
		return nil, err
	}
	s, ok := data.(*preFilterState)
	if !ok {
		return nil, fmt.Errorf("%+v convert to SamplePlugin preFilterState error", data)
	}
	return s, nil
}

func (s *NetworkTraffic) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	preState, err := getPreFilterState(state)
	if err != nil {
		return framework.NewStatus(framework.Error, err.Error())
	}
	// logic
	if nodeInfo.Node().Name == "k8s-node01" || nodeInfo.Node().Name == "k8s-node02" || nodeInfo.Node().Name == "k8s-node03" || nodeInfo.Node().Name == "alicloud-k8s-master01" {
		for k, v := range nodeInfo.Node().Labels {
			if k == "label" && v == "123" {
				klog.V(3).Infof("filter pod : %v, node: %v, pre state: %v", pod.Name, nodeInfo.Node().Name, preState)
				return framework.NewStatus(framework.Success)
			}
		}
		klog.V(3).InfoS("Node doesn't have required label", "node", klog.KObj(nodeInfo.Node()), "label", "123")
		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonNodeLabelNotMatch)
	}
	klog.V(3).Infof("filter pod : %v, node: %v, pre state: %v", pod.Name, nodeInfo.Node().Name, preState)
	return framework.NewStatus(framework.Success)
}

func (s *NetworkTraffic) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
	klog.V(3).Infof("preFilter pod %v", p.Name)
	state.Write(preFilterStateKey, computerPodResourceLimit(p))
	return nil, framework.NewStatus(framework.Success, "PreFilter State updated")
}

func (s *NetworkTraffic) PreFilterExtensions() framework.PreFilterExtensions {
	return nil
}

// type PluginFactory = func(configuration runtime.Object, f framework.Handle) (framework.Plugin, error)
func New(obj runtime.Object, f framework.Handle) (framework.Plugin, error) {
	args, ok := obj.(*config.NetworkTrafficArgs)
	if !ok {
		return nil, fmt.Errorf("[NetworkTraffic] want args to be of type NetworkTrafficArgs, got %T", obj)
	}

	klog.Infof("[NetworkTraffic] args received. NetworkInterface: %s; TimeRangeInMinutes: %d, Address: %s", args.NetworkInterface, args.TimeRangeInMinutes, args.Address)

	return &NetworkTraffic{
		handle:     f,
		prometheus: NewPrometheus(args.Address, args.NetworkInterface, time.Minute*time.Duration(args.TimeRangeInMinutes)),
	}, nil
}

// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
	return Name
}

func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) {
	nodeBandwidth, err := n.prometheus.GetNodeBandwidthMeasure(nodeName)
	if err != nil {
		return 0, framework.NewStatus(framework.Error, fmt.Sprintf("error getting node bandwidth measure: %s", err))
	}

	klog.Infof("[NetworkTraffic] node '%s' bandwidth: %s", nodeName, nodeBandwidth.Value)
	return int64(nodeBandwidth.Value), nil
}

func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions {
	return n
}

func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
	var higherScore int64
	for _, node := range scores {
		if higherScore < node.Score {
			higherScore = node.Score
		}
	}

	// 计算公式为，满分 - (当前带宽 / 最高最高带宽 * 100)
	// 公式的计算结果为，带宽占用越大的机器，分数越低
	for i, node := range scores {
		scores[i].Score = framework.MaxNodeScore - (node.Score * framework.MaxNodeScore / higherScore)
	}

	klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
	return nil
}
